/*
 * Copyright 2007 Matt Jensen
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.jtell.internal.config;

import org.jtell.config.EventSinkMetadata;
import org.jtell.config.OrderMetadata;
import org.jtell.internal.Guard;

import java.util.Map;
import java.util.Set;

/**
 * <p>
 * <code>EventSinkMetadataImpl</code> is the concrete implementation of the {@link EventSinkMetadata} interface.
 * </p>
 * <p>
 * <strong>Thread Safety</strong><br/>
 * Instances of this class are safe for multithreaded access.
 * </p>
 */
public class EventSinkMetadataImpl implements EventSinkMetadata
{
    /**
     * <p>
     * Token order metadata.
     * </p>
     */
    private final Set<OrderMetadata> m_afterOrders;

    /**
     * <p>
     * Additional attributes to be matched by supported events.
     * </p>
     */
    private final Map<String, String> m_attributes;

    /**
     * <p>
     * Before order metadata.
     * </p>
     */
    private final Set<OrderMetadata> m_beforeOrders;

    /**
     * <p>
     * Flag indicating whether this event sink is to be invoked conditionally.
     * </p>
     */
    private final boolean m_conditional;

    /**
     * <p>
     * Tokens contributed by the event sink.
     * </p>
     */
    private final Set<String> m_contributions;

    /**
     * <p>
     * Flag indicating whether the event sink contributes at least one order token.
     * </p>
     */
    private final boolean m_contributor;

    /**
     * <p>
     * The fully qualified event class name.
     * </p>
     */
    private final String m_eventClassName;

    /**
     * <p>
     * Precalculated hash code.
     * </p>
     */
    private final int m_hashCode;

    /**
     * <p>
     * The fully qualified listener class name.
     * </p>
     */
    private final String m_listenerClassName;

    /**
     * <p>
     * The event sink method signature.
     * </p>
     */
    private final String m_methodSignature;

    /**
     * <p>
     * Flag indicating whether the event sink is ordered with respect to at least one order token.
     * </p>
     */
    private final boolean m_ordered;

    /**
     * <p>
     * The fully qualified source class name.
     * </p>
     */
    private final String m_sourceClassName;

    /**
     * <p>
     * Construct a {@link EventSinkMetadataImpl} instance.
     * </p>
     *
     * @param listenerClassName the fully qualified listener class name.
     * @param methodSignature the method signature.
     * @param eventClassName the fully qualified event class name.
     * @param sourceClassName the fully qualified source class name.
     * @param attributes the additional event attributes.
     * @param afterOrders the after order metadata.
     * @param beforeOrders the before order metadata.
     * @param contributions the order tokens contributed by this event sink.
     */
    public EventSinkMetadataImpl(final String listenerClassName,
                                 final String methodSignature,
                                 final String eventClassName,
                                 final String sourceClassName,
                                 final Map<String, String> attributes,
                                 final Set<OrderMetadata> afterOrders,
                                 final Set<OrderMetadata> beforeOrders,
                                 final Set<String> contributions)
    {
        super();
        Guard.notNull("afterOrders", afterOrders);
        Guard.notNull("attributes", attributes);
        Guard.notNull("beforeOrders", beforeOrders);
        Guard.notNull("contributions", contributions);
        Guard.notNull("eventClassName", eventClassName);
        Guard.notNull("listenerClassName", listenerClassName);
        Guard.notNull("methodSignature", methodSignature);
        Guard.notNull("sourceClassName", sourceClassName);

        // Set instance fields.
        m_afterOrders = afterOrders;
        m_attributes = attributes;
        m_beforeOrders = beforeOrders;
        m_contributions = contributions;
        m_eventClassName = eventClassName;
        m_listenerClassName = listenerClassName;
        m_methodSignature = methodSignature;
        m_sourceClassName = sourceClassName;

        // Calculate derived fields.
        m_conditional =
                !attributes.isEmpty() || containsRequiredOrder(afterOrders) || containsRequiredOrder(beforeOrders);
        m_contributor = !contributions.isEmpty();
        m_hashCode = calculateHashCode();
        m_ordered = !afterOrders.isEmpty() || !beforeOrders.isEmpty();
    }

    /* inherit javadoc */
    public boolean equals(final Object baseOther)
    {
        boolean result = false;
        if (this == baseOther)
        {
            result = true;
        }
        else if (null != baseOther && getClass() == baseOther.getClass())
        {
            final EventSinkMetadataImpl other = (EventSinkMetadataImpl) baseOther;
            if (m_hashCode == other.m_hashCode &&
                    m_eventClassName.equals(other.m_eventClassName) &&
                    m_listenerClassName.equals(other.m_listenerClassName) &&
                    m_methodSignature.equals(other.m_methodSignature) &&
                    m_sourceClassName.equals(other.m_sourceClassName) &&
                    m_afterOrders.equals(other.m_afterOrders) &&
                    m_attributes.equals(other.m_attributes) &&
                    m_beforeOrders.equals(other.m_beforeOrders) &&
                    m_contributions.equals(other.m_contributions))
            {
                result = true;
            }
        }
        return result;
    }

    /* inherit javadoc */
    public int hashCode()
    {
        return m_hashCode;
    }

    /* inherit javadoc */
    public Set<OrderMetadata> getAfterOrders()
    {
        return m_afterOrders;
    }

    /* inherit javadoc */
    public Map<String, String> getAttributes()
    {
        return m_attributes;
    }

    /* inherit javadoc */
    public Set<OrderMetadata> getBeforeOrders()
    {
        return m_beforeOrders;
    }

    /* inherit javadoc */
    public Set<String> getContributions()
    {
        return m_contributions;
    }

    /* inherit javadoc */
    public String getEventClassName()
    {
        return m_eventClassName;
    }

    /* inherit javadoc */
    public String getListenerClassName()
    {
        return m_listenerClassName;
    }

    /* inherit javadoc */
    public String getMethodSignature()
    {
        return m_methodSignature;
    }

    /* inherit javadoc */
    public String getSourceClassName()
    {
        return m_sourceClassName;
    }

    /* inherit javadoc */
    public boolean isConditional()
    {
        return m_conditional;
    }

    /* inherit javadoc */
    public boolean isContributor()
    {
        return m_contributor;
    }

    /* inherit javadoc */
    public boolean isOrdered()
    {
        return m_ordered;
    }

    /**
     * <p>
     * Calculate the instance hash code.
     * </p>
     *
     * @return <code>int</code> hash code.
     */
    private int calculateHashCode()
    {
        int result;
        result = m_afterOrders.hashCode();
        result = 31 * result + m_attributes.hashCode();
        result = 31 * result + m_beforeOrders.hashCode();
        result = 31 * result + m_contributions.hashCode();
        result = 31 * result + m_eventClassName.hashCode();
        result = 31 * result + m_listenerClassName.hashCode();
        result = 31 * result + m_methodSignature.hashCode();
        result = 31 * result + m_sourceClassName.hashCode();
        return result;
    }

    /**
     * <p>
     * Determine whether a set of zero or more order metadata objects contains at least one entry that describes a
     * <em>required</em> ordering.
     * </p>
     *
     * @param orders the order metadata objects.
     * @return <code>boolean</code> <code>true</code> if at least one required entry was found.
     */
    private boolean containsRequiredOrder(final Set<OrderMetadata> orders)
    {
        boolean result = false;
        if (!orders.isEmpty())
        {
            for (final OrderMetadata order : orders)
            {
                if (order.isRequired())
                {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }
}
